Les valeurs personnelles
René Magritte - 1952

Contexe et objectif

Ce projet est réalisé par Lili Schmidlin dans le cadre du module IG (Interfaces Graphiques) durant la seconde année de DUT Informatique. Le but est de reproduire un tableau à l'aide d'objets 3D grâce à la librairie Three.js.
Pour ma part, j'ai choisi de reproduire le tableau intitulé Les valeurs personnelles de René Magritte

Le tableau est centré sur l’hypertrophie des objets. L'échelle des objets représentés est modifiée. Le tableau contient les objets suivants : un verre, un peigne, une allumette, un blaireau et un savon.
Ce tableau contient de nombreux aspects intéressants : textures, reflets, volumes et reliefs.

Ci-dessous, à gauche la reproduction 3D du tableau que j'ai réalisé et à droite l'image originale.
En dessous des 2 tableaux, se trouvent les explications sur la conception du tableau 3D.

x y z
Les valeurs personnelles

Architecture et fichiers

Ce projet est composé de 4 fichiers. Ce fichier, index.html, chargé de l'affichage de la scène, et contenant les explications de la construction de cette dernière. Le fichier projet-tableau.js, qui permet la création de la scène et des objets qu'elle contient. Il gère également les évènements. Le fichier style.css assure la mise en page. Et l'image correspond à l'image du tableau d'origine. Enfin le répertoire rep contient les fichiers obj et mlt nécessaires à l'affichage des objets. Les fichiers images sont utilisés pour les textures et la skybox.

Détails de conception de la scène

Objets de la scène
Pour commencer, j'ai importé les fichiers ".obj" nécessaires. Parmi ces objets, j'ai construit le blaireau et le savon à l'aide des logiciels paint3D et Blender. Le lit, l'armoire, le peigne et le verre sont des fichiers ".obj" que j'ai trouvé sur le site Free3D.
J'ai ensuite réalisé les éléments que je pouvais créer moi-même via des box et des sphères qui sont des primitives fournies par Three.js. L'allumette est formée par une BoxGeometry associée à une SphereGeometry. Le petit tapis est également une box, tout comme les 2 plaques qui forment les miroirs de l'armoire. Pour éviter le z-fighting, le petit tapis est placé une unité plus haute que le grand.

Plaquage de texture
Une fois l'ensemble des objets importés, grâce à des OBJLoader, je leur ai appliqué des textures. Le plaquage de texture se fait de différentes manières. Pour le lit, l'armoire et le savon, l'ajout de texture s'est fait à l'aide d'un MLTLoader. Les fichiers .mlt sont des fichiers associés aux modèles obj. Ils déterminent les textures et matériaux des objets. Les caractères diffus, spéculaires et émissifs sont définis dans ces fichiers. Ils peuvent faire appel à des images, comme pour le bois du lit et de l'armoire qui correspond à une image stockée dans le répertoire res (oak.jpg).

Une autre manière d'appliquer une texture à un obj, est d'utiliser un constructeur de Material. Il en existe différents types. On utilisera un MeshPhongMaterial pour le verre, mais un MeshLambertMaterial pour les tapis, l'allumette, les miroirs et les murs. Tandis que l'allumette ne dépend que d'une couleur, les murs et les tapis utilisent l'attribut map dont la valeur sera une variable obtenue par chargement d'une texture image, grâce à un TextureLoader. La seule différence entre les 2 est la manière de les construire. On a vu que le petit était une box, le grand quant à lui, est défini par des coordonnées pures. L'attribut uv est également spécifié afin de pouvoir changer la partie de texture utilisée. Dans notre cas, on utilise l'ensemble de la texture, mais si on avait à utiliser seulement une section de la texture, il suffirait de changer les coordonnées du tableau d'uv de la fonction buildBigCarpet.

Dans le cas des miroirs, l'attribut associé au MeshLambertMaterial sera envMap. Sa valeur est un objet du type WebGLCubeRenderTarget.texture. L'affichage du reflet est réalisé via un objet du type CubeCamera. Le near et le far de cette caméra, ainsi que sa position, sont déterminés afin de voir le savon qui est au-devant de la scène, tout en n'affichant pas l'intérieur de l'armoire.

Les attributs du MeshPhongMaterial du verre sont une couleur et une brillance : shininess. Cet attribut définit le reflet de la lumière dans le verre.

La construction du blaireau m'a demandé quelque temps. L'effet métallisé du manche et les poils ne sont pas facilement réalisables avec Three.js. Pour le manche, j'ai opté pour un matériau du type MeshPhysicalMaterial, dont j'ai défini manuellement les propriétés, telle que la propriété metalness qui permet de donner l'effet brillant. Le reflet est possible grâce à un CubeTextureLoader (même constructeur que pour la skybox expliquée ci-dessous) et la propriété envMap. Les poils sont un obj importé et possédant un matériau réalisé avec Blender. Ils ont été créés à l'aide du mode particules hair de Blender. Ils ne sont pas très discernables de loin, d'où le mode "vue sur pinceau qui permet de se rapprocher et de voir les poils en détails.

Skybox
L'ensemble des éléments sont contenus dans une skybox. Les images qui la composent sont celles chargées sur les murs et le sol de la scène, stockées dans le répertoire rep. La fenêtre sur l'image de droite a été réalisée sur le logiciel de traitement d'images Gimp.

Utilisation des shaders
Le peigne est chargé par un OBJLoader. Cependant, la texture qui lui est appliquée est créée grâce à des shaders. Pour cela, on définit, dans le fichier HTML, les données du shader : le vertex shader et le fragment shader. Le fragment shader contient la fonction de bruit qui sera à l'origine des motifs et mouvements sur le peigne. Dans le fichier JavaScript, via la fonction loadShader, on charge les shaders définis dans le HTML, puis, à partir des données reçues, on créer le matériau qui sera appliqué au peigne.

Tweening et texture transparante
J'ai ajouté au mur en face de la scène, des nuages mobiles. L'image de nuage est appliquée sur un PlaneGeometry, de la même manière que la texture des tapis. Cependant, l'image contient du transparent. Pour que Three.js le prenne en compte, il faut mettre à true, l'attribut transparent du matériau au moment de sa définition.
Pour le déplacement des nuages, j'utilise du tweening. Les variables tweenDebut et tweenFin permettent de définir le mode de tweening, et fait appel aux variables fonctions updateAller et updateRetour. Ces dernières permettent le changement de position suivant une vitesse aléatoire et différente pour chaque nuage (contenue dans le tableau speed). La mise à jour du tweening se fait dans la fonction animate.

Les évènements

Le panel de contrôle est disponible grâce à la librairie dat.gui. Via ce panel, l'utilisateur peut interagir avec la scène et les objets. Les valeurs des sliders et des checkbox que l'utilisateur modifie sont récupérés dans la fonction render. Suivant la valeur du boolean formé par la variable effectController et l'attribut associé, les positions, tailles et couleurs des objets sont modifiés.

Axes
Pour commencer, il peut afficher les 3 axes directionnels : x, y et z. La légende des 3 axes est disponible au-dessus du canvas, aux côtés du panel de statistiques. Ce dernier fournit le nombre de FPS de la scène. À noter que ce sont autour de ces 3 axes que sont réalisées les rotations des objets. C'est suivant les coordonnées x, y et z que sont placés les objets et que sont définies leurs dimensions.

Contrôle des murs
Les contrôles permettent également de jouer sur l'affichage des 4 murs. Si une des checkbox des murs individuels est cochée, celle liée à l'ensemble des murs ne sera par disponible. Si aucune des checkbox (droite, gauche, face et arrière) n'est cochée, alors celle liée aux 4 murs pourra être activée. La disparition/apparition des murs se fait grâce à une modification dynamique de leur taille et de leur position. La méthode animate écoute la scène continuellement. Elle appelle la méthode render chargée de mettre à jour les positions, et autres attributs des objets. Suivant la position de chaque mur, le programme incrémentera ou décrémentera les attributs nécessaires pour faire descendre ou monter les murs. C'est de l'animation incrémentale.

Mode déplacement, plein-écran et point de vue
Deux modes sont disponibles. Le mode "Deplacement", qui permet de déplacer à sa guise l'ensemble des objets de la scène. Cela est réalisable grâce à des EventsControls. A noter que je passe par des objects 3D composés des objets à déplacer et des box dont le matériau est invisible. Sans cela, les points d'ancrage de la souris lors des déplacements, ne seraient pas sur l'objet. De plus, cela est plus pratique pour déplacer les objets formés par plusieurs sous objets (tels que l'allumette ou la zone pinceau/armoire) Les types d'évènements (mouseOut, dragAndDrop et mouseOver), sont paramétrés. Suivant l'action de l'utilisateur (survol, clic ou déplacement), le curseur de la souris changera. Néanmoins, l'utilisateur ne peut déplacer les objets uniquement selon leur positon x et z. Leur hauteur restera inchangée. La désactivation du contrôle caméra est également réalisée, afin de ne pas perturber le déplacement des objets. PS : Si l'on tente de déplacer l'armoire en cliquant sur les poils du pinceau, la fenêtre risque de ne plus répondre... (à cause de la taille de l'obj que représente les poils...)

Un mode de visualisation du type "plein-écran" est disponible. Une fois la case cochée, la scène est agrandie. Elle occupe l'ensemble de la largeur et de la hauteur de la fenêtre. Pour cela, les dimensions du renderer seront window.innerWidth pour la larguer et window.innerHeight pour la hauteur.

Enfin, il est possible de changer de point de vue que l'on a sur la scène. Le premier mode de changement est celui qui permet de voir le pinceau plus en détails. Pour les changements de points de vue, il est nécessaire de passer par une variable intermédiaire qui indique si le point de vue a été activé. Sans cela, le programme ne gère pas correctement le contrôle de la caméra. La seconde précaution à prendre est le détachement des évènements de déplacement. Ils entraînaient également un dysfonctionnement du contrôle caméra.
Pour le changement de position de la caméra et celui de l'endroit observé il suffit de mettre à jour les positions en fonction de celle de l'objet. Si la position de la caméra et celle de la cible, ne sont pas en fonction de l'objet 3D concerné (object3DBed et object3DCloset), après les déplacements, l'angle de vue sur l'objet ne serait plus le bon. Dans le cas du mode où l'utilisateur à l'impression d'être sur le lit, je change la fov (field of view) que j'élargie afin d'avoir un meilleur aperçu de l'ensemble de la pièce. L'appel de la méthode updateProjectionMatrix sur la caméra permet d'enregistrer les modifications effectuées sur la caméra.

Couleurs et motifs du peigne
Le dossier suivant concerne les paramètres du peigne. Le slide "Kd" permet de définir si le peigne sera sombre, ou si au contraire, il donnera l'impression d'émettre de la lumière. Les sliders d'après sont pour modifier le motif du peigne, puis sa couleur.

Lumières de la scène
L'utilisateur peut également changer la position ainsi que la couleur de la lumière du type PointLight. Initialement, elle est située devant la fenêtre fictive. À noter que la scène est constituée de 2 autres types de lumières : une AmbientLight et une DirectionalLight. La première permet uniquement d'éviter les zones mortes. Les positions des lumières ponctuelles et directionnelles sont consultables grâce à la checkbox "Helper" du dossier "Lumière".

Réinitialisation
Enfin, le dossier "Réinitialisation" du panel de contrôle contient les checkbox pour réinitialiser :
- la position de la caméra
- la position des objets
- la couleur et le motif du peigne
- la couleur et la position de la lumière